{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Market Sharing\n",
    "\n",
    "## Objective and Prerequisites\n",
    "\n",
    "In this example, we’ll show you how to solve a goal programming problem that involves allocating the retailers to two divisions of a company in order to optimize the trade-offs of several market sharing goals. You’ll learn how to create a mixed integer linear programming model of the problem using the Gurobi Python API and how to find an optimal solution to the problem using the Gurobi Optimizer.\n",
    "\n",
    "This model is example 13 from the fifth edition of Model Building in Mathematical Programming by H. Paul Williams on pages 267-268 and 322-324.\n",
    "\n",
    "This modeling example is at the beginner level, where we assume that you know Python and that you have some knowledge about building mathematical optimization models. The reader should also consult the  [documentation](https://www.gurobi.com/resources/?category-filter=documentation)\n",
    "of the Gurobi Python API.\n",
    "\n",
    "**Download the Repository** <br /> \n",
    "You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Problem Description\n",
    "\n",
    "A large company has two divisions: D1 and D2. The company supplies retailers with oil and spirit. The goal is to allocate each retailer to either division D1 or division D2. The allocated division will be the retailer’s supplier. As far as possible, the allocation must be made so that D1 controls 40% of the market and D2 the remaining 60%. The retailers in the table below are listed as M1 to M23. Each retailer has an estimated market for oil and spirit. Retailers M1 to M8 are in region 1, retailers M9 to M18 are in region 2, and retailers M19 to M23 are in region 3. Certain retailers are considered to have good growth prospects and categorized as group A and the others are in group B. Each retailer has a certain number of delivery points. \n",
    "![retailers](retailers.PNG)\n",
    "\n",
    "We want to make the 40%/60% split between D1 and D2 in each of the following categories:\n",
    "1. Total number of delivery points\n",
    "2. Control of spirit market\n",
    "3. Control of oil market in region 1\n",
    "4. Control of oil market in region 2\n",
    "5. Control of oil market in region 3\n",
    "6. Number of retailers in group A\n",
    "7. Number of retailers in group B.\n",
    "\n",
    "There is flexibility in that any market share may vary by $\\pm$ 5%. That is, the share can vary between the limits 35%/65% and 45%/55%. The objective is to minimize the sum of the percentage deviations from the 40%/60% split."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Formulation\n",
    "\n",
    "### Sets and Indices\n",
    "\n",
    "$r \\in \\text{Retailers}=\\{\\ 1,2,...,23\\}$\n",
    "\n",
    "### Parameters\n",
    "\n",
    "$\\text{deliveryPoints}_{r} \\in \\mathbb{N}^+$: Delivery points of retailer $r$.\n",
    "\n",
    "$\\text{spiritMarket}_{r} \\in \\mathbb{R}^+$: Spirit market -in millions of gallons, of retailer $r$.\n",
    "\n",
    "$\\text{oilMarket1}_{r} \\in \\mathbb{R}^+$: Oil market -in millions of gallons of retailer $r$ in Region 1.\n",
    "\n",
    "$\\text{oilMarket2}_{r} \\in \\mathbb{R}^+$: Oil market -in millions of gallons of retailer $r$ in Region 2.\n",
    "\n",
    "$\\text{oilMarket3}_{r} \\in \\mathbb{R}^+$: Oil market -in millions of gallons of retailer $r$ in Region 3.\n",
    "\n",
    "$\\text{retailerA}_{r} \\in \\{0,1\\}$: Parameter has a value of 1 if retailer $r$  belongs to group A.\n",
    "\n",
    "$\\text{retailerB}_{r} \\in \\{0,1\\}$: Parameter has a value of 1 if retailer $r$  belongs to group B.\n",
    "\n",
    "$\\text{deliveryPoints40} \\in \\mathbb{R}^+$: Forty percent of the delivery points.\n",
    "\n",
    "$\\text{deliveryPoints5} \\in \\mathbb{R}^+$: Five percent of the delivery points.\n",
    "\n",
    "$\\text{spiritMarket40} \\in \\mathbb{R}^+$: Forty percent of  the spirit market.\n",
    "\n",
    "$\\text{spiritMarket5} \\in \\mathbb{R}^+$: Five percent of the spirit market.\n",
    "\n",
    "$\\text{oilMarket1_40} \\in \\mathbb{R}^+$: Forty percent of  the oil market in region 1.\n",
    "\n",
    "$\\text{oilMarket1_5} \\in \\mathbb{R}^+$: Five percent of  the oil market in region 1.\n",
    "\n",
    "$\\text{oilMarket2_40} \\in \\mathbb{R}^+$: Forty percent of  the oil market in region 2.\n",
    "\n",
    "$\\text{oilMarket2_5} \\in \\mathbb{R}^+$: Five percent of  the oil market in region 2.\n",
    "\n",
    "$\\text{oilMarket3_40} \\in \\mathbb{R}^+$: Forty percent of  the oil market in region 3.\n",
    "\n",
    "$\\text{oilMarket3_5} \\in \\mathbb{R}^+$: Five percent of  the oil market in region 3.\n",
    "\n",
    "$\\text{retailerA40} \\in \\mathbb{R}^+$: Forty percent of the number of retailers in group A.\n",
    "\n",
    "$\\text{retailerA5} \\in \\mathbb{R}^+$: Five percent of the number of retailers in group A.\n",
    "\n",
    "$\\text{retailerB40} \\in \\mathbb{R}^+$: Forty percent of the number of retailers in group B.\n",
    "\n",
    "$\\text{retailerB5} \\in \\mathbb{R}^+$: Five percent of the number of retailers in group B.\n",
    "\n",
    "### Decision Variables\n",
    "\n",
    "$\\text{allocate}_{r} \\in \\{0,1\\}$: This binary variable is equal 1, if retailer r is allocated to Division 1, and 0 if allocated to Division 2.\n",
    "\n",
    "$\\text{deliveryPointsPos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent of the delivery points.\n",
    "\n",
    "$\\text{deliveryPointsNeg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent of the delivery points.\n",
    "\n",
    "$\\text{spiritMarketPos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent  of the spirit market.\n",
    "\n",
    "$\\text{spiritMarketNeg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent  of the spirit market.\n",
    "\n",
    "$\\text{oilMarket1Pos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent  of  the oil market in region 1.\n",
    "\n",
    "$\\text{oilMarket1Neg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent  of  the oil market in region 1.\n",
    "\n",
    "$\\text{oilMarket2Pos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent  of  the oil market in region 2.\n",
    "\n",
    "$\\text{oilMarket2Neg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent  of the oil market in region 2.\n",
    "\n",
    "$\\text{oilMarket3Pos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent  of  the oil market in region 3.\n",
    "\n",
    "$\\text{oilMarket3Neg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent  of the oil market in region 3.\n",
    "\n",
    "$\\text{retailerAPos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent of the number of retailers in group A.\n",
    "\n",
    "$\\text{retailerANeg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent of the number of retailers in group A.\n",
    "\n",
    "$\\text{retailerBPos} \\in \\mathbb{R}^+$: This decision variable measures the positive deviation of the retailers’ allocation for the goal of satisfying forty percent of the number of retailers in group B.\n",
    "\n",
    "$\\text{retailerBNeg} \\in \\mathbb{R}^+$: This decision variable measures the negative deviation of the retailers’ allocation for the goal of satisfying forty percent of the number of retailers in group B.\n",
    "\n",
    "### Constraints\n",
    "\n",
    "**Delivery points**: The allocation of retailers at Division 1 satisfies as much as possible forty percent of the delivery points.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{deliveryPoints}_{r}*{\\text{allocate}_{r}} + \\text{deliveryPointsPos} - \\text{deliveryPointsNeg}  = \\text{deliveryPoints40}\n",
    "\\end{equation}\n",
    "\n",
    "**Spirit Market**: The allocation of retailers at Division 1 satisfies as much as possible forty percent of the spirit market.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{spiritMarket}_{r}*{\\text{allocate}_{r}} + \\text{spiritMarketPos} - \n",
    "\\text{spiritMarketNeg}  = \\text{spiritMarket40}\n",
    "\\end{equation}\n",
    "\n",
    "**Oil market region 1**: The allocation of retailers in region 1 at Division 1 satisfies as much as possible forty percent of the oil market in that region.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{oilMarket1}_{r}*{\\text{allocate}_{r}} + \\text{oilMarket1Pos} - \n",
    "\\text{oilMarket1Neg}  = \\text{oilMarket1_40}\n",
    "\\end{equation}\n",
    "\n",
    "**Oil market region 2**: The allocation of retailers in region 2 at Division 1 satisfies as much as possible forty percent of the oil market in that region.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{oilMarket2}_{r}*{\\text{allocate}_{r}} + \\text{oilMarket2Pos} - \n",
    "\\text{oilMarket2Neg}  = \\text{oilMarket2_40}\n",
    "\\end{equation}\n",
    "\n",
    "**Oil market region 3**: The allocation of retailers in region 3 at Division 1 satisfies as much as possible forty percent of the oil market in that region.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{oilMarket3}_{r}*{\\text{allocate}_{r}} + \\text{oilMarket3Pos} - \n",
    "\\text{oilMarket3Neg}  = \\text{oilMarket3_40}\n",
    "\\end{equation}\n",
    "\n",
    "**Group A**: The allocation of retailers at Division 1 satisfies as much as possible forty percent of the retailers in group A.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{retailerA40}_{r}*{\\text{allocate}_{r}} + \\text{retailerAPos} - \n",
    "\\text{retailerANeg}  = \\text{retailerA40}\n",
    "\\end{equation}\n",
    "\n",
    "**Group B**: The allocation of retailers at Division 1 satisfies as much as possible forty percent of the retailers in group B.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{r \\in \\text{Retailers}} \\text{retailerB40}_{r}*{\\text{allocate}_{r}} + \\text{retailerBPos} - \n",
    "\\text{retailerBNeg}  = \\text{retailerB40}\n",
    "\\end{equation}\n",
    "\n",
    "**Flexibility**: There is flexibility in that any market share may vary by $\\pm$ 5%.\n",
    "\n",
    "$$\n",
    "\\text{deliveryPointsPos} \\leq \\text{deliveryPoints5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{deliveryPointsNeg}  \\leq \\text{deliveryPoints5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{spiritMarketPos} \\leq \\text{spiritMarket5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{spiritMarketNeg}  \\leq \\text{spiritMarket5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{oilMarket1Pos} \\leq \\text{oilMarket1_5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{oilMarket1Neg} \\leq \\text{oilMarket1_5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{oilMarket2Pos} \\leq \\text{oilMarket2_5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{oilMarket2Neg} \\leq \\text{oilMarket2_5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{oilMarket3Pos} \\leq \\text{oilMarket3_5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{oilMarket3Neg} \\leq \\text{oilMarket3_5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{retailerAPos} \\leq \\text{retailerA5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{retailerANeg} \\leq \\text{retailerA5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{retailerBPos} \\leq \\text{retailerB5}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{retailerBNeg} \\leq \\text{retailerB5}\n",
    "$$\n",
    "\n",
    "### Objective Function\n",
    "\n",
    "**Minimize deviations**: Minimize the sum of positive and negative deviations.\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{Minimize} \\quad  \\text{deliveryPointsPos} + \\text{deliveryPointsNeg} + \\text{spiritMarketPos} + \\text{spiritMarketNeg} +\n",
    "\\text{oilMarket1Pos} + \\text{oilMarket1Neg}\n",
    "\\end{equation}\n",
    "\n",
    "$$\n",
    "+ \\text{oilMarket2Pos} + \\text{oilMarket2Neg} + \\text{oilMarket3Pos} + \\text{oilMarket3Neg} \n",
    "$$\n",
    "\n",
    "$$\n",
    "+ \\text{retailerAPos} + \\text{retailerANeg} + \\text{retailerBPos} + \\text{retailerBNeg}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Python Implementation\n",
    "\n",
    "We import the Gurobi Python Module and other Python libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install gurobipy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from itertools import product\n",
    "\n",
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "\n",
    "# tested with Python 3.11 & Gurobi 11.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Input data\n",
    "\n",
    "We define all the input data for the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create a dictionary to capture the delivery points and spirit market -in millions of gallons.\n",
    "\n",
    "retailers, deliveryPoints, spiritMarket = gp.multidict({\n",
    "    (1): [11,34],\n",
    "    (2): [47,411],\n",
    "    (3): [44,82],\n",
    "    (4): [25,157],\n",
    "    (5): [10,5],\n",
    "    (6): [26,183],\n",
    "    (7): [26,14],\n",
    "    (8): [54,215],\n",
    "    (9): [18,102],\n",
    "    (10): [51,21],\n",
    "    (11): [20,54],\n",
    "    (12): [105,0],\n",
    "    (13): [7,6],\n",
    "    (14): [16,96],\n",
    "    (15): [34,118],\n",
    "    (16): [100,112],\n",
    "    (17): [50,535],\n",
    "    (18): [21,8],\n",
    "    (19): [11,53],\n",
    "    (20): [19,28],\n",
    "    (21): [14,69],\n",
    "    (22): [10,65],\n",
    "    (23): [11,27]\n",
    "})\n",
    "\n",
    "# Create a dictionary to capture the oil market -in millions of gallons for region 1.\n",
    "\n",
    "retailers1,  oilMarket1 = gp.multidict({\n",
    "    (1): 9,\n",
    "    (2): 13,\n",
    "    (3): 14,\n",
    "    (4): 17,\n",
    "    (5): 18,\n",
    "    (6): 19,\n",
    "    (7): 23,\n",
    "    (8): 21\n",
    "})\n",
    "\n",
    "# Create a dictionary to capture the oil market -in millions of gallons for region 2.\n",
    "\n",
    "retailers2,  oilMarket2 = gp.multidict({\n",
    "    (9): 9,\n",
    "    (10): 11,\n",
    "    (11): 17,\n",
    "    (12): 18,\n",
    "    (13): 18,\n",
    "    (14): 17,\n",
    "    (15): 22,\n",
    "    (16): 24,\n",
    "    (17): 36,\n",
    "    (18): 43\n",
    "})\n",
    "\n",
    "# Create a dictionary to capture the oil market -in millions of gallons for region 3.\n",
    "\n",
    "retailers3,  oilMarket3 = gp.multidict({\n",
    "    (19): 6,\n",
    "    (20): 15,\n",
    "    (21): 15,\n",
    "    (22): 25,\n",
    "    (23): 39\n",
    "})\n",
    "\n",
    "# Create a dictionary to capture retailers in group A.\n",
    "\n",
    "groupA,  retailerA = gp.multidict({\n",
    "    (1): 1,\n",
    "    (2): 1,\n",
    "    (3): 1,\n",
    "    (5): 1,\n",
    "    (6): 1,\n",
    "    (10): 1,\n",
    "    (15): 1,\n",
    "    (20): 1\n",
    "})\n",
    "\n",
    "# Create a dictionary to capture retailers in group B.\n",
    "\n",
    "groupB,  retailerB = gp.multidict({\n",
    "    (4): 1,\n",
    "    (7): 1,\n",
    "    (8): 1,\n",
    "    (9): 1,\n",
    "    (11): 1,\n",
    "    (12): 1,\n",
    "    (13): 1,\n",
    "    (14): 1,\n",
    "    (16): 1,\n",
    "    (17): 1,\n",
    "    (18): 1,\n",
    "    (19): 1,\n",
    "    (21): 1,\n",
    "    (22): 1,\n",
    "    (23): 1\n",
    "})\n",
    "\n",
    "# Forty and five percentages of each goal\n",
    "\n",
    "deliveryPoints40 = 292\n",
    "deliveryPoints5 = 36.5\n",
    "spiritMarket40 = 958\n",
    "spiritMarket5 = 119.75\n",
    "oilMarket1_40 = 53.6\n",
    "oilMarket1_5 = 6.7\n",
    "oilMarket2_40 = 86\n",
    "oilMarket2_5 = 10.75\n",
    "oilMarket3_40 = 40\n",
    "oilMarket3_5 = 5\n",
    "retailerA40 = 3.2\n",
    "retailerA5 = 0.4\n",
    "retailerB40 = 6\n",
    "retailerB5 = 0.75"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Deployment\n",
    "\n",
    "We create a model and the variables. The main decision variable is a binary variable that is equal to 1  when a retailer is allocated to Division 1, and 0 when allocated it to Division 2. The rest of the decision variables measure positive and negative deviations from the seven goals of the 40%/60% split."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using license file c:\\gurobi\\gurobi.lic\n"
     ]
    }
   ],
   "source": [
    "model = gp.Model('MarketSharing')\n",
    "\n",
    "# Allocation of retailers to Division 1.\n",
    "allocate = model.addVars(retailers, vtype=GRB.BINARY, name=\"allocate\")\n",
    "\n",
    "# Positive and negative deviation of delivery points goal.\n",
    "\n",
    "deliveryPointsPos = model.addVar(ub= deliveryPoints5, name='deliveryPointsPos')\n",
    "deliveryPointsNeg = model.addVar(ub= deliveryPoints5, name='deliveryPointsNeg')\n",
    "\n",
    "# Positive and negative deviation of spirit market goal.\n",
    "\n",
    "spiritMarketPos = model.addVar(ub=spiritMarket5, name='spiritMarketPos')\n",
    "spiritMarketNeg = model.addVar(ub=spiritMarket5, name='spiritMarketNeg')\n",
    "\n",
    "# Positive and negative deviation of oil market in region 1 goal.\n",
    "\n",
    "oilMarket1Pos = model.addVar(ub=oilMarket1_5, name='oilMarket1Pos')\n",
    "oilMarket1Neg = model.addVar(ub=oilMarket1_5, name='oilMarket1Neg')\n",
    "\n",
    "# Positive and negative deviation of oil market in region 2 goal.\n",
    "\n",
    "oilMarket2Pos = model.addVar(ub=oilMarket2_5, name='oilMarket2Pos')\n",
    "oilMarket2Neg = model.addVar(ub=oilMarket2_5, name='oilMarket2Neg')\n",
    "\n",
    "# Positive and negative deviation of oil market in region 3 goal.\n",
    "\n",
    "oilMarket3Pos = model.addVar(ub=oilMarket3_5, name='oilMarket3Pos')\n",
    "oilMarket3Neg = model.addVar(ub=oilMarket3_5, name='oilMarket3Neg')\n",
    "\n",
    "# Positive and negative deviation of retailers in group A goal.\n",
    "\n",
    "retailerAPos  = model.addVar(ub=retailerA5, name='retailerAPos')\n",
    "retailerANeg  = model.addVar(ub=retailerA5, name='retailerANeg')\n",
    "\n",
    "# Positive and negative deviation of retailers in group B goal.\n",
    "\n",
    "retailerBPos  = model.addVar(ub=retailerB5, name='retailerBPos')\n",
    "retailerBNeg  = model.addVar(ub=retailerB5, name='retailerBNeg')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers at Division 1 satisfies as much as possible forty percent of the delivery points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Delivery points constraint.\n",
    "\n",
    "DPConstr = model.addConstr((gp.quicksum(deliveryPoints[r]*allocate[r] for r in retailers) \n",
    "                            + deliveryPointsPos - deliveryPointsNeg == deliveryPoints40), name='DPConstrs')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers at Division 1 satisfies as much as possible forty percent of the spirit market."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Spirit market constraint.\n",
    "\n",
    "SMConstr = model.addConstr((gp.quicksum(spiritMarket[r]*allocate[r] for r in retailers) \n",
    "                            + spiritMarketPos - spiritMarketNeg == spiritMarket40), name='SMConstr')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers in region 1 at Division 1 satisfies as much as possible forty percent of the oil market in that region."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Oil market in region 1 constraint.\n",
    "\n",
    "OM1Constr = model.addConstr((gp.quicksum(oilMarket1[r]*allocate[r] for r in retailers1) \n",
    "                            + oilMarket1Pos - oilMarket1Neg == oilMarket1_40), name='OM1Constr')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers in region 2 at Division 1 satisfies as much as possible forty percent of the oil market in that region."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Oil market in region 2 constraint.\n",
    "\n",
    "OM2Constr = model.addConstr((gp.quicksum(oilMarket2[r]*allocate[r] for r in retailers2) \n",
    "                            + oilMarket2Pos - oilMarket2Neg == oilMarket2_40), name='OM2Constr')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers in region 3 at Division 1 satisfies as much as possible forty percent of the oil market in that region."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Oil market in region 3 constraint.\n",
    "\n",
    "OM3Constr = model.addConstr((gp.quicksum(oilMarket3[r]*allocate[r] for r in retailers3) \n",
    "                            + oilMarket3Pos - oilMarket3Neg == oilMarket3_40), name='OM3Constr')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers at Division 1 satisfies as much as possible forty percent of the retailers in group A."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Group A constraint.\n",
    "\n",
    "AConstr = model.addConstr((gp.quicksum(retailerA[r]*allocate[r] for r in groupA) \n",
    "                            + retailerAPos - retailerANeg == retailerA40), name='AConstr')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The allocation of retailers at Division 1 satisfies as much as possible forty percent of the retailers in group B."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Group B constraint.\n",
    "\n",
    "BConstr = model.addConstr((gp.quicksum(retailerB[r]*allocate[r] for r in groupB) \n",
    "                            + retailerBPos - retailerBNeg == retailerB40), name='BConstr')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Minimize the sum of positive and negative deviations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Objective function\n",
    "\n",
    "obj = deliveryPointsPos + deliveryPointsNeg+ spiritMarketPos + spiritMarketNeg + oilMarket1Pos + oilMarket1Neg + oilMarket2Pos + oilMarket2Neg + oilMarket3Pos + oilMarket3Neg + retailerAPos + retailerANeg + retailerBPos + retailerBNeg \n",
    "\n",
    "model.setObjective(obj)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)\n",
      "Thread count: 4 physical cores, 8 logical processors, using up to 8 threads\n",
      "Optimize a model with 7 rows, 37 columns and 105 nonzeros\n",
      "Model fingerprint: 0xa5aab3a9\n",
      "Variable types: 14 continuous, 23 integer (23 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [1e+00, 5e+02]\n",
      "  Objective range  [1e+00, 1e+00]\n",
      "  Bounds range     [4e-01, 1e+02]\n",
      "  RHS range        [3e+00, 1e+03]\n",
      "Presolve time: 0.00s\n",
      "Presolved: 7 rows, 37 columns, 105 nonzeros\n",
      "Variable types: 14 continuous, 23 integer (23 binary)\n",
      "\n",
      "Root relaxation: objective 0.000000e+00, 13 iterations, 0.00 seconds\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0    0.00000    0    7          -    0.00000      -     -    0s\n",
      "H    0     0                     131.6000000    0.00000   100%     -    0s\n",
      "H    0     0                     111.6000000    0.00000   100%     -    0s\n",
      "H    0     0                      70.6000000    0.00000   100%     -    0s\n",
      "H    0     0                      66.8000000    0.00000   100%     -    0s\n",
      "     0     0    0.20000    0    6   66.80000    0.20000   100%     -    0s\n",
      "H    0     0                      63.8000000    0.20000   100%     -    0s\n",
      "H    0     0                      54.8000000    0.20000   100%     -    0s\n",
      "H    0     0                      42.4235294    0.20000   100%     -    0s\n",
      "H    0     0                      39.3737179    0.20000  99.5%     -    0s\n",
      "H    0     0                      38.8000000    0.20000  99.5%     -    0s\n",
      "     0     0    0.20000    0    7   38.80000    0.20000  99.5%     -    0s\n",
      "H    0     0                      30.8000000    0.20000  99.4%     -    0s\n",
      "H    0     0                      24.8000000    0.20000  99.2%     -    0s\n",
      "     0     0    0.60000    0   11   24.80000    0.60000  97.6%     -    0s\n",
      "H    0     0                       7.6000000    0.60000  92.1%     -    0s\n",
      "     0     0    0.60000    0    7    7.60000    0.60000  92.1%     -    0s\n",
      "     0     0    0.60000    0   10    7.60000    0.60000  92.1%     -    0s\n",
      "     0     0    0.60000    0   10    7.60000    0.60000  92.1%     -    0s\n",
      "     0     0    0.60000    0   10    7.60000    0.60000  92.1%     -    0s\n",
      "     0     0    0.60000    0    8    7.60000    0.60000  92.1%     -    0s\n",
      "     0     0    0.67457    0    6    7.60000    0.67457  91.1%     -    0s\n",
      "     0     0    0.92289    0   11    7.60000    0.92289  87.9%     -    0s\n",
      "     0     0    6.72359    0    8    7.60000    6.72359  11.5%     -    0s\n",
      "     0     0    7.38975    0    8    7.60000    7.38975  2.77%     -    0s\n",
      "     0     2    7.38975    0    8    7.60000    7.38975  2.77%     -    0s\n",
      "\n",
      "Cutting planes:\n",
      "  Gomory: 1\n",
      "  Cover: 39\n",
      "  MIR: 24\n",
      "  StrongCG: 3\n",
      "  Inf proof: 5\n",
      "  RLT: 4\n",
      "  Relax-and-lift: 1\n",
      "\n",
      "Explored 439 nodes (2410 simplex iterations) in 0.11 seconds\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 10: 7.6 24.8 30.8 ... 131.6\n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 7.599999999999e+00, best bound 7.599999999999e+00, gap 0.0000%\n"
     ]
    }
   ],
   "source": [
    "# Verify model formulation\n",
    "\n",
    "model.write('marketSharing.lp')\n",
    "\n",
    "# Run optimization engine\n",
    "\n",
    "model.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Analysis\n",
    "\n",
    "The allocation of retailers to Division 1 that minimizes the sum of positive and negative deviations from the goal follows. In addition, we show how each goal is within the 35%/45% range values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "_________________________________________________________________________________\n",
      "The optimal allocation of retailers to Division 1 is:\n",
      "_________________________________________________________________________________\n",
      "Retailer2\n",
      "Retailer6\n",
      "Retailer7\n",
      "Retailer9\n",
      "Retailer12\n",
      "Retailer13\n",
      "Retailer14\n",
      "Retailer15\n",
      "Retailer23\n"
     ]
    }
   ],
   "source": [
    "# Output reports\n",
    "\n",
    "print(\"\\n\\n_________________________________________________________________________________\")\n",
    "print(f\"The optimal allocation of retailers to Division 1 is:\")\n",
    "print(\"_________________________________________________________________________________\")\n",
    "for r in retailers:\n",
    "    if(allocate[r].x > 0.5):\n",
    "        print(f\"Retailer{r}\")\n",
    "\n",
    "#print(f\"\\nThe optimal objective function value is {model.objVal}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following report validates that the goals have been satisfied within acceptable 35% and 45% ranges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Goal</th>\n",
       "      <th>Min_35</th>\n",
       "      <th>Actual</th>\n",
       "      <th>Max_45</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Delivery points</td>\n",
       "      <td>255.50</td>\n",
       "      <td>290.0</td>\n",
       "      <td>328.50</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Spirit market</td>\n",
       "      <td>838.25</td>\n",
       "      <td>957.0</td>\n",
       "      <td>1077.75</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Oil market1</td>\n",
       "      <td>46.90</td>\n",
       "      <td>55.0</td>\n",
       "      <td>60.30</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Oil market2</td>\n",
       "      <td>75.25</td>\n",
       "      <td>84.0</td>\n",
       "      <td>96.75</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Oil market3</td>\n",
       "      <td>35.00</td>\n",
       "      <td>39.0</td>\n",
       "      <td>45.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Group A</td>\n",
       "      <td>2.80</td>\n",
       "      <td>3.0</td>\n",
       "      <td>3.60</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Group B</td>\n",
       "      <td>5.25</td>\n",
       "      <td>6.0</td>\n",
       "      <td>6.75</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             Goal  Min_35  Actual   Max_45\n",
       "  Delivery points  255.50   290.0   328.50\n",
       "    Spirit market  838.25   957.0  1077.75\n",
       "      Oil market1   46.90    55.0    60.30\n",
       "      Oil market2   75.25    84.0    96.75\n",
       "      Oil market3   35.00    39.0    45.00\n",
       "          Group A    2.80     3.0     3.60\n",
       "          Group B    5.25     6.0     6.75"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Test that the solution is within acceptable ranges.\n",
    "\n",
    "DeliveryPointsGoal = sum([deliveryPoints[r] for r in retailers if allocate[r].x > 0.5])\n",
    "spiritMarketGoal = sum([spiritMarket[r] for r in retailers if allocate[r].x > 0.5])\n",
    "oilMarket1Goal = sum([oilMarket1[r] for r in retailers1 if allocate[r].x > 0.5])\n",
    "oilMarket2Goal = sum([oilMarket2[r] for r in retailers2 if allocate[r].x > 0.5])\n",
    "oilMarket3Goal = sum([oilMarket3[r] for r in retailers3 if allocate[r].x > 0.5])\n",
    "retailerAGoal = sum([retailerA[r] for r in groupA if allocate[r].x > 0.5])\n",
    "retailerBGoal = sum([retailerB[r] for r in groupB if allocate[r].x > 0.5])\n",
    "\n",
    "goal_ranges = pd.DataFrame({\n",
    "    \"Goal\": [\"DeliveryPoints\", \"Spirit market\", \"Oil market1\", \"Oil market1\", \"Oil market3\", \"Group A\", \"Group B\"],\n",
    "    \"Min_35\": [round(val*(0.35/0.40),2) for val in (deliveryPoints40, spiritMarket40, oilMarket1_40, oilMarket2_40, oilMarket3_40, retailerA40, retailerB40)],\n",
    "    \"Actual\": [round(val,2) for val in (DeliveryPointsGoal, spiritMarketGoal, oilMarket1Goal, oilMarket2Goal, oilMarket3Goal, retailerAGoal, retailerBGoal)],\n",
    "    \"Max_45\": [round(val*(0.45/0.40),2)  for val in (deliveryPoints40, spiritMarket40, oilMarket1_40, oilMarket2_40, oilMarket3_40, retailerA40, retailerB40)],\n",
    "})\n",
    "goal_ranges.index=[''] * len(goal_ranges)\n",
    "goal_ranges"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "H. Paul Williams, Model Building in Mathematical Programming, fifth edition.\n",
    "\n",
    "Copyright © 2020 Gurobi Optimization, LLC"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
